#include "shared.h"
#include "urb.h"






/****************************
*        ALTSETTING         *
****************************/

//Traverse the alternate setting list and find one that provides
//the least bandwidth that satisfies the minimum requirement.
int usbcam_choose_altsetting( struct driverInstance* this ){
	
	struct usb_interface *intf;
	const struct usb_host_interface *aintf;
	const struct usb_endpoint_descriptor* endpointDescriptor = NULL;
	int i, j;
	
	int endpointMaxPacketSize;
	int bandwidth;
	int best_alt = -1, best_alt_bw = 0;
	
	int interfaceNumber = this->urbstreamInterfaceNumber;
	int pipe = usb_rcvisocpipe( this->usbDevice, this->urbstreamEndpoint );
	
	
	if( atomic_read(&this->usbDeviceIsDisconnected) ){
		printk( KERN_WARNING "[%s] %s: device is disconnected\n", KBUILD_MODNAME, __FUNCTION__ );
		return -ENODEV;
	}
	
	if( interfaceNumber < 0 ) interfaceNumber = this->usbInterface->cur_altsetting->desc.bInterfaceNumber;
	
	intf = usb_ifnum_to_if( this->usbDevice, interfaceNumber );
	if( !intf ) {
		printk( KERN_WARNING "[%s] %s: interface %d does not exist\n", KBUILD_MODNAME, __FUNCTION__, interfaceNumber );
		return -ENODEV;
	}
	
	for (i = 0; i < intf->num_altsetting; i++) {
		
		aintf = &intf->altsetting[i];
		for (j = 0; j < aintf->desc.bNumEndpoints; j++) {
			endpointDescriptor = &aintf->endpoint[j].desc;
			if( (endpointDescriptor->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) == usb_pipeendpoint(pipe) )break;
		}
		
		if ( j == aintf->desc.bNumEndpoints ) {
			//Desired endpoint not present in this descriptor
			printk( KERN_NOTICE "[%s] altsetting %d has no EP%d\n", KBUILD_MODNAME, i, usb_pipeendpoint(pipe) );
			continue;
		}
		
		if ( !usb_endpoint_xfer_isoc(endpointDescriptor) || !usb_endpoint_dir_in(endpointDescriptor) ){
			//Something is horribly wrong
			printk( KERN_NOTICE "[%s] altsetting %d has unexpected EP%d\n", KBUILD_MODNAME, i, usb_pipeendpoint(pipe) );
			continue;
		}
		
		bandwidth = 0;
		endpointMaxPacketSize = le16_to_cpu( endpointDescriptor->wMaxPacketSize );
		
		if( this->usbDevice->speed == USB_SPEED_HIGH ){
			//8 uframes per regular frame
			bandwidth = 8000;
			
			//high bandwidth endpoint?
			endpointMaxPacketSize = (endpointMaxPacketSize & 0x7ff) * (((endpointMaxPacketSize >> 11) & 0x3) + 1);
		}else{
			bandwidth = 1000;
			endpointMaxPacketSize &= 0x7ff;
		}
		
		bandwidth *= endpointMaxPacketSize;
		
		//Divide by interval / frame skippage
		bandwidth = bandwidth / (1 << (endpointDescriptor->bInterval - 1));
		
		printk( KERN_NOTICE "[%s] altsetting %d provides %d B/s bandwidth\n", KBUILD_MODNAME, i, bandwidth );
		
		//Check the bandwidth
		if( bandwidth < this->webcamCurrentResolution->requiredBandwidth ) continue;
		
		//Check the packet size
		if( (this->urbstreamMinPacketSize >= 0) && (endpointMaxPacketSize < this->urbstreamMinPacketSize) ) continue;
		
		if( (best_alt < 0) || (bandwidth < best_alt_bw) ){
			best_alt = i;
			best_alt_bw = bandwidth;
		}
	}
	
	if (best_alt == -1) return -ENODEV;
	
	this->usbCurrentAltsetting = best_alt;
	return 0;
}


/****************************
*          CLEANUP          *
****************************/

static void usbcam_urb_free( struct urb* urbp ){
	usb_free_coherent( urbp->dev, urbp->transfer_buffer_length, urbp->transfer_buffer, urbp->transfer_dma );
	//usb_buffer_free( urbp->dev, urbp->transfer_buffer_length, urbp->transfer_buffer, urbp->transfer_dma );
	usb_free_urb( urbp );
}

void usbcam_urbstream_cleanup( struct driverInstance* this ){
	int i;
	for( i = 0; i < URBSTREAM_URB_COUNT; i++ ) if( this->urbstreamUrbs[i].urbp ){
		usbcam_urb_free( this->urbstreamUrbs[i].urbp );
		this->urbstreamUrbs[i].urbp = NULL;
	}
}


/****************************
*        WORK PROCESS       *
****************************/

//NEVER SLEEP IN A COMPLETION HANDLER.
//These are often called in atomic context.
static void urbCompleteHandler( struct urb* urb ){
	struct urb_capsule* urbCapsule = urb->context;
	struct driverInstance* this = urbCapsule->driverInstanceThis;
	if( urb->status == -ECONNRESET || urb->status == -ENOENT ) return;//usb_kill_urb() or usb_unlink_urb()
	if( !queue_work( this->urbstreamWorkqueue, &urbCapsule->workitem ) ) printk( KERN_ERR "[%s] %s: queue_work err\n", KBUILD_MODNAME, __FUNCTION__ );
}

static void usbcam_urbstream_iso_process( struct work_struct* work ){
	
	struct urb_capsule* urbCapsule = container_of( work, struct urb_capsule, workitem );
	struct driverInstance* this = urbCapsule->driverInstanceThis;
	struct urb* urbp = urbCapsule->urbp;
	
	int i = 0;
	int res = 0;
	
	char* buf;
	int len;
	int status;
	
	if( !atomic_read( &this->urbstreamIsStreaming ) ) return;
	
	//printk( KERN_NOTICE "[%s] urbstream[%d] processing %p\n", KBUILD_MODNAME, this->us_endpoint, ibp );
	
	for( i = 0; i < urbp->number_of_packets; i++ ){
		
		buf = (((char *) urbp->transfer_buffer) + urbp->iso_frame_desc[i].offset);
		len = urbp->iso_frame_desc[i].actual_length;
		status = urbp->iso_frame_desc[i].status;
		
		urbp->iso_frame_desc[i].actual_length = 0;
		urbp->iso_frame_desc[i].status = 0;
		
		//void r5u870_iso_packet_done( struct driverInstance* this, const uint8_t *pktdata, int pktlen, int pktstatus )
		r5u870_iso_packet_done( this, buf, len, status );
	}
	
	if( atomic_read( &this->urbstreamIsStreaming ) ){
		res = usb_submit_urb( urbp, GFP_ATOMIC );
		if( res == -EL2NSYNC ) res = usb_submit_urb( urbp, GFP_ATOMIC );
		
		if( res ){
			printk( KERN_NOTICE "[%s] urbstream[%d] resubmit %p failed: %d\n", KBUILD_MODNAME, this->urbstreamEndpoint, urbCapsule, res );
			webcamCaptureStop( this );
		}
	}
}

/****************************
*        STOP START         *
****************************/

void usbcam_urbstream_stop( struct driverInstance* this ){
	
	int i = 0;
	atomic_set( &this->urbstreamIsStreaming, 0 );
	for( i = 0; i < URBSTREAM_URB_COUNT; i++ ) usb_kill_urb( this->urbstreamUrbs[i].urbp );
}


int usbcam_urbstream_start( struct driverInstance* this ){
	
	int res = 0;
	int i = 0;
	
	if( atomic_read( &this->urbstreamIsStreaming ) ){
		printk( KERN_WARNING "[%s] %s: urbstream[%d] already streaming\n", KBUILD_MODNAME, __FUNCTION__, this->urbstreamEndpoint );
		return -EEXIST;
	}
	
	atomic_set( &this->urbstreamIsStreaming, 1 );
	
	for( i = 0; i < URBSTREAM_URB_COUNT; i++ ){
		
		res = usb_submit_urb( this->urbstreamUrbs[i].urbp, GFP_KERNEL );
		if( res == -EL2NSYNC ) res = usb_submit_urb( this->urbstreamUrbs[i].urbp, GFP_KERNEL );
		if( res ){
			printk( KERN_ERR "[%s] %s: URB submit failed: %d\n", KBUILD_MODNAME, __FUNCTION__, res );
			printk( KERN_ERR "[%s] urbstream[%d] start failed: %i\n", KBUILD_MODNAME, this->urbstreamEndpoint, res );
			usbcam_urbstream_stop( this );
			break;
		}
	}
	
	return res;
}


/****************************
*          PREPARE          *
****************************/

struct urb* usbcam_urb_alloc( struct driverInstance* this, int packetSize ){
	
	struct urb* urbp;
	int totalBytesCount;
	int i;
	
	urbp = usb_alloc_urb( URBSTREAM_URB_PACKETS_COUNT, GFP_KERNEL );
	if( !urbp ) return NULL;
	
	urbp->dev = this->usbDevice;
	urbp->pipe = usb_rcvisocpipe( this->usbDevice, this->urbstreamEndpoint );
	urbp->number_of_packets = URBSTREAM_URB_PACKETS_COUNT;
	urbp->transfer_buffer = NULL;
	urbp->transfer_buffer_length = 0;
	
	totalBytesCount = packetSize * URBSTREAM_URB_PACKETS_COUNT;
	
	urbp->transfer_buffer = usb_alloc_coherent( urbp->dev, totalBytesCount, GFP_KERNEL, &urbp->transfer_dma );
	//urbp->transfer_buffer = usb_buffer_alloc( urbp->dev, totalBytesCount, GFP_KERNEL, &urbp->transfer_dma );
	
	if( !urbp->transfer_buffer ){
		usb_free_urb( urbp );
		return NULL;
	}
	
	urbp->transfer_buffer_length = totalBytesCount;
	urbp->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
	urbp->transfer_flags |= URB_ISO_ASAP;
	
	for(i = 0; i < URBSTREAM_URB_PACKETS_COUNT; i++){
		urbp->iso_frame_desc[i].offset = (i * packetSize);
		urbp->iso_frame_desc[i].length = packetSize;
		urbp->iso_frame_desc[i].actual_length = 0;
		urbp->iso_frame_desc[i].status = 0;
	}
	
	urbp->complete = urbCompleteHandler;
	urbp->interval = 1;//FIXME: find the appropriate interval for the endpoint
	
	return urbp;
}

int usbcam_urbstream_config_iso( struct driverInstance* this ){
	
	struct usb_host_endpoint* endpoint;
	
	int err = 0;
	int packetSize = 0;
	int i;
	
	
	endpoint = this->usbDevice->ep_in[this->urbstreamEndpoint];
	if( !endpoint ){
		printk( KERN_ERR "[%s] Endpoint %d is not exist\n", KBUILD_MODNAME, this->urbstreamEndpoint );
		return -EINVAL;
	}
	
	packetSize = __le16_to_cpu( endpoint->desc.wMaxPacketSize );
	if ( !packetSize ) {
		printk( KERN_ERR "[%s] urbstream[%d]: current endpoint has maxpacket=0\n", KBUILD_MODNAME, this->urbstreamEndpoint );
		return -EINVAL;
	}
	
	if ( this->usbDevice->speed == USB_SPEED_HIGH ) packetSize = (packetSize & 0x7ff) * (((packetSize >> 11) & 0x3) + 1);
	else packetSize &= 0x7ff;
	
	printk( KERN_NOTICE "[%s] urbstream[%d] using packetSize %d\n", KBUILD_MODNAME, this->urbstreamEndpoint, packetSize );
	
	for( i = 0; i < URBSTREAM_URB_COUNT; i++ ){
		
		this->urbstreamUrbs[i].urbp = usbcam_urb_alloc( this, packetSize );
		if( !this->urbstreamUrbs[i].urbp ){
			usbcam_urbstream_cleanup( this );
			err = -ENOMEM;
			break;
		}
		
		this->urbstreamUrbs[i].urbp->context = &this->urbstreamUrbs[i];
		this->urbstreamUrbs[i].driverInstanceThis = this;
		INIT_WORK( &this->urbstreamUrbs[i].workitem, usbcam_urbstream_iso_process );
	}
	
	return err;
}
